/*
 * Aritmetico.cpp
 *
 *  Created on: 03/06/2010
 *      Author: sebastian
 */


#include "Aritmetico.h"

		Aritmetico::Aritmetico(){
			this->archivoComprimido = NULL;
			this->rangoInferior = 0;
			this->rangoSuperior = 0xFFFFFFFF;
			this->underflow = 0;
			this->buffer = new unsigned[TAM_BUFFER];
			this->indiceBuffer = 0;
			this->indiceActual = 0;
			this->actual = 0;
		}

		Aritmetico::~Aritmetico(){
			delete[] this->buffer;
		}

		void Aritmetico::asignarArchivo(FILE* archivoDestino){
			this->archivoComprimido = archivoDestino;
		}

		void Aritmetico::comprimir(Contexto* contexto, TLetraExt* letra){
			if(this->archivoComprimido){
				//caracter, (piso,techo)
				TIntervalo intervalo;
				if(contexto){
					armarIntervalo(contexto,intervalo);
				}else{
					armarIntervaloMenosUno(*letra,intervalo);
				}
				TIntervalo::iterator* it;
				if(letra == NULL){
					it = new TIntervalo::iterator;
					(*it) = intervalo.end();
					if(intervalo.begin()!=intervalo.end()){
						(*it)--;
						//piso del ESC (techo + 1 del ultimo caracter)
						this->rangoInferior = (*it)->second.second + 1;
					} //si no solo hay un esc en el intervalo... no cambia nada
				}else{
					it = buscarCaracter(*letra, intervalo);
					//abro el scope del caracter
					this->rangoInferior = (*it)->second.first;
					this->rangoSuperior = (*it)->second.second;
				}
				delete it;
				if(letra){
					if(*letra == FIN_DE_ARCHIVO){
						//emito el piso
						emitirPiso();
					}else{
						unsigned total = verificarOverflow();
						if(total>0)
							emitirOverflow(total);
						else{
							total = verificarUnderflow();
							if(total>0)
								manejarUnderflow(total);
						}
					}
				}else{
					unsigned total = verificarOverflow();
					if(total>0)
						emitirOverflow(total);
					else{
						total = verificarUnderflow();
						if(total>0)
							manejarUnderflow(total);
					}
				}
			}
		}

		void Aritmetico::inicializarNumero(){
			if(this->archivoComprimido){
				delete[] buffer;
				this->buffer = new unsigned[TAM_BUFFER];
				this->indiceActual = 0;
				this->indiceBuffer = 0;
				fread(this->buffer,4,TAM_BUFFER,this->archivoComprimido);
				//en una primera lectura lleno actual
				this->actual = this->buffer[this->indiceBuffer];
				this->indiceBuffer++;
			}
		}

		TLetraExt* Aritmetico::descomprimir(Contexto* contexto){
			if(this->archivoComprimido){
				//caracter, (piso,techo)
				TIntervalo intervalo;
				TLetraExt* caracter = NULL;
				if(contexto){
					this->armarIntervalo(contexto,intervalo);
					if(intervalo.begin() != intervalo.end()){
						TIntervalo::iterator* it = buscarNumero(intervalo);
						if(it){
							caracter = new TLetraExt((*it)->first);
							this->rangoInferior = (*it)->second.first;
							this->rangoSuperior = (*it)->second.second;
						}else{ //es un ESC
							it = new TIntervalo::iterator;
							(*it) = intervalo.end();
							(*it)--;
							//piso del ESC (techo + 1 del ultimo caracter)
							this->rangoInferior = (*it)->second.second + 1;
						}
						delete it;
					}else{ // sino es un intervalo con ESC(1)... no hago nada
						return NULL;
					}
				}else{
					//busca el caracter por su offset
					caracter = buscarIntervaloMenosUno();
				}
				if(caracter){
					if(*caracter != FIN_DE_ARCHIVO){
						unsigned total = this->verificarOverflow();
						if(total>0){
							resolverOverflow(total);
						}else{
							total = this->verificarUnderflow();
							if(total>0){
								resolverUnderflow(total);
							}
						}
					}
				}else{
					unsigned total = this->verificarOverflow();
					if(total>0){
						resolverOverflow(total);
					}else{
						total = this->verificarUnderflow();
						if(total>0){
							resolverUnderflow(total);
						}
					}
				}
				return caracter;
			}
			return NULL;
		}

		TLetraExt* Aritmetico::buscarIntervaloMenosUno(){
			//cada caracter equiprobable 1/257
			//offset = ascii del caracter
			bool encontrado = false;
			unsigned L = this->rangoSuperior - this->rangoInferior;
			unsigned piso = this->rangoInferior;
			unsigned techo = 0;
			TLetraExt letra = 0;
			bool eof = false;
			while((!encontrado)&&(!eof)){
				techo = floor(piso+L/257)-1;
				if((this->actual>=piso)&&(this->actual<=techo)){
					encontrado = true;
				}else{
					if(letra == 255) eof = true;
					letra++;
					piso = techo + 1;
				}
			}
			if(eof){
				techo = floor(piso+L/257)-1;
				this->rangoInferior = piso;
				this->rangoSuperior = techo;
				return new TLetraExt(letra);
			}
			this->rangoInferior = piso;
			this->rangoSuperior = techo;
			return new TLetraExt(letra);
		}

		TIntervalo::iterator* Aritmetico::buscarCaracter(TLetraExt letra, TIntervalo & intervalo){
			TIntervalo::iterator* it = new TIntervalo::iterator;
			bool yaEsta = false;
			(*it) = intervalo.begin();
			/*if(*it != intervalo.end())
				++(*it);*/
			while((!yaEsta)&&((*it) != intervalo.end())){
				if((*it)->first == letra){
					yaEsta = true;
				}else{
					++(*it);
				}
			}
			return it;
		}

		TIntervalo::iterator* Aritmetico::buscarNumero(TIntervalo & intervalo){
			TIntervalo::iterator* it = new TIntervalo::iterator;
			bool yaEsta = false;
			(*it) = intervalo.begin();
			//++(*it);
			while((!yaEsta)&&((*it) != intervalo.end())){
				if(((*it)->second.first <= actual)&&((*it)->second.second >= actual)){
					yaEsta = true;
				}else{
					++(*it);
				}
			}
			if(!yaEsta){
				delete it;
				return NULL;
			}
			return it;
		}

		void Aritmetico::armarIntervaloMenosUno(TLetraExt letra, TIntervalo& intervalo){
			//cada caracter equiprobable 1/257
			//offset = ascii del caracter
			unsigned L = this->rangoSuperior - this->rangoInferior;
			unsigned piso = floor(this->rangoInferior+(L/257)*letra);// + letra;
			unsigned techo = floor(piso+L/257)-1;
			PISO_TECHO par (piso,techo);
			intervalo.insert(pair<TLetraExt,PISO_TECHO >(letra,par));
		}

		void Aritmetico::armarIntervalo(Contexto* contexto, TIntervalo& intervalo){
			unsigned L = this->rangoSuperior - this->rangoInferior;
			unsigned piso = rangoInferior;
			unsigned techo = 0;
			TTablaFreq::iterator it;
			TTablaFreq *tabla = contexto->getTabla();
			for(it = tabla->begin() ; it != tabla->end() ; ++it){
				//cout << endl;
				//cout << "techo:"<<rangoSuperior<<endl;
				//cout << "piso:"<<rangoInferior<<endl;
				techo = floor(((L/contexto->getFrecuenciaTotal())*(it->second))+piso)-1;
				////cout << "L:" << L<<endl;
				////cout << "proba:" << it->second << endl;
				////cout << "total:" << contexto->getFrecuenciaTotal() << endl;
				////cout << "techo:"<<techo<<endl;
				////cout << "piso:"<<piso<<endl;
				////cout << "char:"<<it->first<<endl;
				PISO_TECHO par (piso,techo);
				intervalo.insert(pair<TLetra,PISO_TECHO> (it->first,par));
				piso = techo + 1;
			}
		}

		unsigned Aritmetico::verificarUnderflow(){
			unsigned total = 0;
			//primero me fijo que el techo sea 1... y el piso 0...
			if((this->rangoSuperior & 0x80000000)&&(~this->rangoInferior & 0x80000000)){
				// 1 solo para techo = 0 piso = 1
				unsigned resultado = ~((rangoSuperior << 1) | (~(rangoInferior << 1)));
                while (resultado & 0x80000000) {
                        //multiplico x 2
						resultado <<= 1;
                        total++;
                }
			}
			return total;
		}

		void Aritmetico::manejarUnderflow(unsigned total){
		//cout<<"underflow:"<<total<<endl;
    		this->underflow += total;
    		calcularNuevoTechoUnderflow(total);
    		calcularNuevoPisoUnderflow(total);
			//si hubo cambios me fijo si hay overflow
    		unsigned totalO = verificarOverflow();
    		if(totalO > 0){
    			emitirOverflow(totalO);
    		}
		}

		void Aritmetico::resolverUnderflow(unsigned total){
    		this->underflow += total;
    		calcularNuevoTechoUnderflow(total);
    		calcularNuevoPisoUnderflow(total);
			descartarUnderflow(total);
    		//si hubo cambios me fijo si hay overflow
    		unsigned totalO = verificarOverflow();
    		if(totalO > 0){
    			resolverOverflow(totalO);
    		}
		}

		void Aritmetico::descartarUnderflow(unsigned total){
			unsigned aux = this->actual;
			//me quedo con el bit mas significativo
			aux >>= 31;
			aux <<= 31;
			//corro la ventana
			for(int j=0;j<(int)total;j++)
				descartarBit();
			//this->actual <<= total;
			//this->actual >>=total;
			this->actual &= 0x7FFFFFFF;
			//regenero el bit mas significativo
			this->actual |= aux;
		}

		void Aritmetico::descartarBit(){
			this->actual <<= 1;
			unsigned aux = this->buffer[this->indiceBuffer] >> 31;
			this->buffer[this->indiceBuffer] <<=1;
			this->actual |= aux;
			this->indiceActual++;
			if(this->indiceActual==32){
				this->indiceBuffer++;
				this->indiceActual = 0;
				if(this->indiceBuffer == TAM_BUFFER){
					leerArchivo();
				}
			}
		}

		void Aritmetico::resolverOverflow(unsigned total){
			//la cantidad de numeros a "descartar"
			//total += this->underflow;
			//this->underflow = 0;
			unsigned aux = total;
			while(aux > 0){
				descartarBit();
				aux--;
			}
			calcularNuevoTechoOverflow(total);
			calcularNuevoPisoOverflow(total);
			//si hubo cambios me fijo si hay underflow
			unsigned totalU = verificarUnderflow();
			if(totalU > 0){
				resolverUnderflow(totalU);
			}
		}

		unsigned Aritmetico::verificarOverflow(){
			//¬XOR me indica los bits que son iguales
			unsigned resultado = ~(this->rangoSuperior ^ this->rangoInferior);
	        unsigned total = 0;
	        ////std:://cout<< "Numero " << this->rangoSuperior << "-" << this->rangoInferior << std::endl;

			//me fijo la cantidad de 1's mas significativos del ¬XOR
	        while (resultado & 0x80000000) {
					//multiplico por dos
					resultado <<= 1;
	                total++;
	        }
	        //esos bits los tengo que emitir
	        return total;
		}

		void Aritmetico::emitirOverflow(unsigned total){
			//cout<<"overflow:"<<total<<endl;
			unsigned aux = total;
			//el primero y los de underflow
			unsigned bitActual = rangoSuperior >> 31;
			agregarBit(bitActual);
			bitActual = ((~bitActual) << 31) >> 31;
			while(this->underflow >0){
			   	agregarBit(bitActual);
				this->underflow--;
			}
			aux--;
			unsigned aux2 = rangoSuperior<< 1;
			// los demas
			while(aux > 0){
				bitActual = aux2 >> 31;
				agregarBit(bitActual);
				aux2 <<=1;
				aux--;
			}
			calcularNuevoTechoOverflow(total);
			calcularNuevoPisoOverflow(total);
			//si hubo cambios me fijo si hay underflow
			unsigned totalU = verificarUnderflow();
			if(totalU > 0){
				manejarUnderflow(totalU);
			}
		}

		void Aritmetico::agregarBit(unsigned bit){
			this->actual <<= 1;
			this->actual |= bit;
			this->indiceActual++;
			if(this->indiceActual == 32){
				agregarByte();
				this->indiceActual = 0;
				this->actual = 0;
			}
		}

		void Aritmetico::agregarByte(){
			this->buffer[indiceBuffer] = this->actual;
			this->indiceBuffer++;
			if(this->indiceBuffer == TAM_BUFFER){
				escribirArchivo();
				this->indiceBuffer = 0;
				delete[] this->buffer;
				this->buffer = new unsigned[TAM_BUFFER];
			}
		}

		void Aritmetico::escribirArchivo(){
			fwrite(this->buffer,4,TAM_BUFFER,this->archivoComprimido);
		}

		void Aritmetico::leerArchivo(){
			delete[] buffer;
			this->buffer = new unsigned[TAM_BUFFER];
			fread(this->buffer,4,TAM_BUFFER,this->archivoComprimido);
			this->indiceBuffer = 0;
		}

		void Aritmetico::emitirPiso(){
			/*unsigned pisoActual = this->rangoInferior;
        	unsigned bitActual = 0;
        	for(int i=0;i<32;i++){
				bitActual = pisoActual >> 31;
				agregarBit(bitActual);
				pisoActual = pisoActual << 1;
        	}*/
    		emitirOverflow(32);
        	//ahora debo completar el numero actual con padding
        	//while((indiceActual%8)!=0){
    		while(indiceActual!=0){
        		agregarBit(0);
        	}
        	//escribo el buffer hasta donde lo tengo
        	 if(this->indiceBuffer>0){
        	  	fwrite(this->buffer,4,this->indiceBuffer,this->archivoComprimido);
        	 }
        	//escribo estos ultimos 0-4 bytes
        	/*if(indiceActual!=0){
        		fwrite(&actual+4-indiceActual/8,1,indiceActual/8,this->archivoComprimido);
        	}*/
		}

		void Aritmetico::calcularNuevoTechoOverflow(unsigned total){
			//cout<<"total:"<<total<<endl;
			//cout<<"antes (techo):"<<rangoSuperior<<endl;
			this->rangoSuperior<<=total;
			//genero los 1's a concatenar
			unsigned unos = pow(2,total) - 1;
			//OR
			this->rangoSuperior |= unos;
			////cout<<"dps (techo):"<<rangoSuperior<<endl;
		}

		void Aritmetico::calcularNuevoPisoOverflow(unsigned total){
			////cout<<"total:"<<total<<endl;
			////cout<<"antes (piso):"<<rangoInferior<<endl;
			this->rangoInferior<<=total;
			////cout<<"dps (piso):"<<rangoInferior<<endl;
		}

		void Aritmetico::calcularNuevoTechoUnderflow(unsigned total){
			this->rangoSuperior<<= total;
			//genero el 1 de adelante
			this->rangoSuperior |= 0x80000000;
			//genero los 1's a concatenar
			unsigned unos = pow(2,total) - 1;
			//OR
			this->rangoSuperior |= unos;
		}

		void Aritmetico::calcularNuevoPisoUnderflow(unsigned total){
			this->rangoInferior<<=total;
			//genero el 0 de adelante
			this->rangoInferior &= 0x7FFFFFFF;
		}

